#pragma once

#include "OrionFlames.h"
#include "Variation.h"

namespace OrionFlames
{
class OFAPI xForm
{
public:
	float m_Weight;//This transform's weight.
	vec2 m_X, m_Y, m_O;//Linear part of transform.
	vec2 m_RotatedX, m_RotatedY, m_RotatedO;//Rotated values or original x, y and o. These are what's used to draw.
	float m_Wvar;//Nonlinear weight. This is scale on the original GUI.
	Variation* m_Var;//Nonlinear portion of transform.
	std::string m_Tag;//GLSL tag to append to function names. Note: The whole user settable "tag" is bad design. It should automatically be set when adding the xForm to a preset.
	
	inline float Weight() { return m_Weight; }
	inline void Weight(float weight) { m_Weight = weight; }
	inline float NonlinearWeight() { return m_Wvar; }
	inline void NonlinearWeight(float nonlinearWeight) { m_Wvar = nonlinearWeight; }
	inline vec2& X() { return m_X; }
	inline void X(vec2 x) { m_X = x; }
	inline vec2& Y() { return m_Y; }
	inline void Y(vec2 y) { m_Y = y; }
	inline vec2& O() { return m_O; }
	inline void O(vec2 o) { m_O = o; }

	inline vec2& RotatedX() { return m_RotatedX; }
	inline void RotatedX(vec2 rotatedx) { m_RotatedX = rotatedx; }
	inline vec2& RotatedY() { return m_RotatedY; }
	inline void RotatedY(vec2 rotatedy) { m_RotatedY = rotatedy; }
	inline vec2& RotatedO() { return m_RotatedO; }
	inline void RotatedO(vec2 rotatedo) { m_RotatedO = rotatedo; }

	inline string& Tag() { return m_Tag; }//This must return a reference or else managed code will crash when calling.
	inline void Tag(string tag) { m_Tag = tag; }
	inline Variation* Var() { return m_Var; }
	inline void Var(Variation* var) { m_Var = var; }
	inline string VariationName() { return m_Var->getName(); }

	xForm(std::string tag = "")
		: m_Weight(0.0),
		  m_X(1.0,0.0), m_Y(0.0,1.0), m_O(0.0,0.0),
		  m_RotatedX(m_X), m_RotatedY(m_Y), m_RotatedO(m_O),
		  m_Wvar(1.0), m_Var(0), m_Tag(tag)
	{
	}

	xForm(const xForm& xform)
	{
		m_Weight = xform.m_Weight;
		m_X = xform.m_X;
		m_Y = xform.m_Y;
		m_O = xform.m_O;
		m_RotatedX = m_X;
		m_RotatedY = m_Y;
		m_RotatedO = m_O;
		m_Wvar = xform.m_Wvar;
		m_Var = xform.m_Var;
		m_Tag = xform.m_Tag;
	}

	//Used to verify cached xForm parameters.
	//Need a more robust verifier that checks all members - Matt.
	bool operator==(const xForm &b) const
	{
		return m_Var == b.m_Var;
	}
	
	//Used to initialize from draves-style file.
	void Init(float w, Variation* var, float v, const char* coef_str, string tag)
	{
		//static int count = 0;
		m_Tag = tag;
		m_Weight = w;
		sscanf_s(coef_str, "%f %f %f %f %f %f", &m_X.x, &m_X.y, &m_Y.x, &m_Y.y, &m_O.x, &m_O.y);
		m_RotatedX = m_X;
		m_RotatedY = m_Y;
		m_RotatedO = m_O;
		m_Wvar = v;
		m_Var = var;
	}

	void Init(float w, Variation* var_ptr, const char* coef_str, string tag)
	{
		Init(w, var_ptr, 1.0, coef_str, m_Tag);
	}
	
	//Map this location in 2D space.
	vec2 Apply(const vec2 &src)
	{
		vec2 t = (m_X * src.x) + (m_Y * src.y) + m_O;
		return m_Wvar * m_Var->apply(t);
	}
	
	//Return offical human-readable name of this variation.
	std::string GetName() const
	{
		return "xForm" + m_Tag;
	}
	
	//Return GLSL-compatible code that reads a vec2 "t" and writes to the vec2 "p".
	std::string GetCode()
	{
		std::string ret = getUniformDecl() +
			"vec2 applyMap" + m_Tag + "(vec2 t)\n" +
			"{\n" +
			"	t = xf" + m_Tag + "[0] * t.x + xf" + m_Tag + "[1] * t.y + xf" + m_Tag + "[2];\n" + 
			" 	vec2 p;\n"
			"	{\n" +
			"		" + m_Var->getCode() + "\n" +
			"	}\n" +
			"	return xf" + m_Tag + "[6].x * p;\n"
			"}\n\n" +
			"float jacobian" + m_Tag + "(vec2 t)\n" +
			"{\n" +
			"	" + m_Var->getAreaCode() + "\n" +
			"}\n\n" +
			"float getDensity(vec2 t)\n" +
			"{\n" +
			"	t = xf" + m_Tag + "[0] * t.x + xf" + m_Tag + "[1] * t.y + xf" + m_Tag + "[2];\n"
			"	return 1.0 / (1.0e-3 + abs(xf" + m_Tag + "[7].x * jacobian" + m_Tag + "(t)));\n"
			"}\n\n";

		//std::cout << "Shader map GLSL code: " << ret << "\n\n\n";
		return ret;
	}
	
	//Return GLSL code that calls the function f for each inverse. 
	//Code chunks involved:
	//	g: plane coords to texture samples (with main, outside)
	//	f: invert linear transform, call g
	//	sumInverse: call f at each nonlinear inverse
	//	main: call sumInverse at plane coords (also outside)
	std::string GetInverseCode()
	{
		return  getUniformDecl() +
			"float jacobian" + m_Tag + "(vec2 t)\n" +
			"{\n" +
			"	" + m_Var->getAreaCode() + "\n" +
			"}\n\n" +

			"//Called by nonlinear inverters, invert linear part of map.\n" +
			"vec4 f" + m_Tag + "(vec2 inv)\n" + 
			"{\n" + 
			"	float areaScale = 1.0 / (1.0e-2 + abs(xf" + m_Tag + "[7].x * jacobian" + m_Tag + "(inv)));\n" +
			"	vec2 p = xf" + m_Tag + "[3] * inv.x + xf" + m_Tag + "[4] * inv.y + xf" + m_Tag + "[5];\n" +
			"	return areaScale * g" + m_Tag + "(p);\n"
			"}\n\n" +

			"vec4 nonlinear_inverse" + m_Tag + "(vec2 p)\n" +
			"{\n" +
			"	p = p * xf" + m_Tag + "[6].y;\n" +
			"	" + sed(m_Var->getInverseCode(), "f(", "f" + m_Tag + "(") + "\n" +
			"}\n\n";
	}
	
	//Return the affine (non-Variation) component of our jacobian determinant.
	float GetAffineArea() const
	{
		return fabs(m_Wvar * m_Wvar * (m_RotatedX.x * m_RotatedY.y - m_RotatedX.y * m_RotatedY.x));
	}
	
	//Apply our uniforms to this shader
	void SetUniforms(GLhandleARB prog) const
	{
		vec2 xf[m_Uniforms];
		GetUniforms(xf);
		float* f = xf[0];
		glUniform2fvARB(glGetUniformLocationARB(prog, ("xf" + m_Tag).c_str()), m_Uniforms, f);
	}
	
	//Return the Jacobian determinant at this point.
	float GetArea(const vec2 &src)
	{
		vec2 t = m_RotatedX * src.x + m_Y * src.y + m_RotatedO;
		return m_Var->getArea(t) * GetAffineArea();
	}
	
	//Return the GLSL code that returns the float jacobian
	//determinant, given the float source location (x,y).
	/*std::string getAreaCode()
	{
		return "NOOOO!!!!!  xForm::getAreaCode not implemented";
	}*/
private:
	std::string getUniformDecl() const
	{
		return "uniform vec2 xf" + m_Tag + "[8]; // Forward xyo[0-2], inverse XYO[3-5], vec2(m_Wvar,1.0/m_Wvar)[6], affinearea[7]\n\n";
	}

	//Fill this vector with our uniform variables "xf" + m_Tag.
	void GetUniforms(vec2* xf) const
	{
		xf[0] = m_RotatedX;
		xf[1] = m_RotatedY;
		xf[2] = m_RotatedO;
		makeInverseMatrix(xf[3], xf[4], xf[5]);
		xf[6] = vec2(m_Wvar, 1.0 / m_Wvar);
		xf[7] = vec2(GetAffineArea(), 0.0);
	}

	//Make X, Y, and O columns of the inverse of our linear matrix x,y,o.
	//Goal:
	//	b=x*a.x+y*a.y+o; //Forward transform, in apply.
	//	a=X*b.x+Y*b.y+O; //Inverse transform.
	void makeInverseMatrix(vec2 &X, vec2 &Y, vec2 &O) const
	{
		float idet = 1.0f / (m_RotatedX.x * m_RotatedY.y - m_RotatedX.y * m_RotatedY.x);
		X.x = idet * m_RotatedY.y;
		Y.x = idet * -m_RotatedY.x;
		X.y = idet * -m_RotatedX.y;
		Y.y = idet * m_RotatedX.x;
		O = -(X * m_RotatedO.x + Y * m_RotatedO.y); /* see a==0 case */
	}

	//Number of GLSL uniform vec2's in array below.
	const static int m_Uniforms = 8;
};
}